#include <stdio.h>

#include "memenv.h"
#include "dobj.h"
#include "mvect.h"
#include "dobjauxlib.h"


#define	PDOBJ_PVEND	DOBJ_INTERNAL_ENCODE(1)
#define	PDOBJ_SPACE	DOBJ_INTERNAL_ENCODE(2)
#define	PDOBJ_IDX	DOBJ_INTERNAL_ENCODE(3)
#define	PDOBJ_DOT	DOBJ_INTERNAL_ENCODE(4)
#define	PDOBJ_LIST	DOBJ_INTERNAL_ENCODE(5)

#define	WRITE_ESCAPE_CHAR(ch0, ch1, cur, l, b)		do {		\
	(b)[(cur)++] = ch0;						\
	if ((cur) >= (l)) {						\
		return	(l);						\
	}								\
	(b)[(cur)++] = ch1;						\
} while (0)

static	int
write_string(struct dobj_string *sobj, char *str, int len)
{
	int	i, d;
	char	ch;

	for (i = 0, d = 0; (i < sobj->dobj_str_length) && (d < len); i++) {
		ch = sobj->dobj_str_data[i];
		switch (ch) {
		case	'\n':
			WRITE_ESCAPE_CHAR('\\', 'n', d, len, str);
			break;

		case	'\r':
			WRITE_ESCAPE_CHAR('\\', 'r', d, len, str);
			break;

		case	'\t':
			WRITE_ESCAPE_CHAR('\\', 't', d, len, str);
			break;

		case	'"':
			WRITE_ESCAPE_CHAR('\\', '"', d, len, str);
			break;

		case	'\\':
			WRITE_ESCAPE_CHAR('\\', '\\', d, len, str);
			break;

		default:
			str[d++] = ch;
			break;
		}
	}

	return	(d);
}

int
write_dobj(struct memenv *env, dobj_ptr_t obj, dobj_ptr_t *res)
{
	char	buf[32], *ptr;
	struct	vector	dfvect, fmtvect;
	dobj_ptr_t	cur;
	int	err, len, xl, list;

	err = -1;
	INIT_VECTOR(&fmtvect);
	INIT_VECTOR(&dfvect);
	if (vector_append_dobj(&dfvect, obj) < 0) {
		goto	fini;
	}
	list = 0;
	while (!VECTOR_EMPTY(&dfvect)) {
		int	t, e;
		len = 0;
		cur = vector_pop_dobj(&dfvect);
		if (cur == PDOBJ_PVEND) {
			len = 1;
			buf[0] = ')';
			goto	next;
		}
		if (cur == PDOBJ_SPACE) {
			len = 1;
			buf[0] = ' ';
			goto	next;
		}
		if (cur == PDOBJ_DOT) {
			len = 3;
			buf[0] = ' ';
			buf[1] = '.';
			buf[2] = ' ';
			goto	next;
		}
		if (cur == PDOBJ_LIST) {
			list = 1;
			continue;
		}
		if (cur == PDOBJ_IDX) {
			struct	dobj_vector	*vecobj;
			dobj_ptr_t	vec;
			uint32_t	idx, *idxp;
			vec = vector_pop_dobj(&dfvect);
			idx = *(uint32_t*)VECTOR_POP_OBJ(&dfvect, sizeof(idx));
			vecobj = MENV_GET_OBJ(env, DOBJ_PTR(vec));
			cur = vecobj->dobj_vec_data[idx];
			if (idx > 0) {
				buf[len++] = ' ';
			}
			idx++;
			if (idx < vecobj->dobj_vec_length) {
				idxp = vector_append_obj(&dfvect,
						sizeof(*idxp));
				*idxp = idx;
				vector_append_dobj(&dfvect, vec);
				vector_append_dobj(&dfvect, PDOBJ_IDX);
			}
		}
		t = DOBJ_TYPE(cur);
		if (t == DOBJT_PAIR) {
			struct	dobj_pair	*pair;
			pair = MENV_GET_OBJ(env, DOBJ_PTR(cur));
			if (list == 0) {
				buf[len++] = '(';
				if (vector_append_dobj(&dfvect,
							PDOBJ_PVEND) < 0) {
					goto	fini;
				}
			}
			if (pair->dobj_cdr != DOBJ_NULL) {
				if (vector_append_dobj(&dfvect,
							pair->dobj_cdr) < 0) {
					goto	fini;
				}
				t = DOBJ_TYPE(pair->dobj_cdr);
				if (t == DOBJT_PAIR) {
					e = vector_append_dobj(&dfvect,
							PDOBJ_LIST);
					if (e < 0) {
						goto	fini;
					}
					e = vector_append_dobj(&dfvect,
							PDOBJ_SPACE);
					if (e < 0) {
						goto	fini;
					}
				} else {
					e = vector_append_dobj(&dfvect,
							PDOBJ_DOT);
					if (e < 0) {
						goto	fini;
					}
				}
			}
			if (vector_append_dobj(&dfvect, pair->dobj_car) < 0) {
				goto	fini;
			}
			list = 0;
		} else if (t == DOBJT_VECTOR) {
			struct	dobj_vector	*vecobj;
			buf[len++] = '#';
			buf[len++] = '(';
			vecobj = MENV_GET_OBJ(env, DOBJ_PTR(cur));
			if (vecobj->dobj_vec_length > 0) {
				uint32_t	*idxp;
				if (vector_append_dobj(&dfvect,
							PDOBJ_PVEND) < 0) {
					goto	fini;
				}
				idxp = vector_append_obj(&dfvect,
						sizeof(*idxp));
				if (idxp == NULL) {
					goto	fini;
				}
				*idxp = 0;
				if ((vector_append_dobj(&dfvect, cur) < 0) ||
				    (vector_append_dobj(&dfvect,
							PDOBJ_IDX) < 0)) {
					goto	fini;
				}
			} else {
				buf[len++] = ')';
			}
		} else if (t == DOBJT_SIMPLE) {
			switch (cur) {
			case	DOBJ_NULL:
				buf[len++] = '\'';
				buf[len++] = '(';
				buf[len++] = ')';
				break;
			case	DOBJ_TRUE:
				buf[len++] = '#';
				buf[len++] = 't';
				break;
			case	DOBJ_FALSE:
				buf[len++] = '#';
				buf[len++] = 'f';
				break;
			default:
				if (DOBJ_CHAR(cur)) {
					xl = snprintf(buf + len,
							sizeof(buf) - len,
							"%c", (char)cur);
					len += xl;
				} else if (DOBJ_FINT(cur)) {
					int	x = DOBJ_INT_VALUE(cur);
					xl = snprintf(buf + len,
							sizeof(buf) - len,
							"%d", x);
					len += xl;
				} else {
					xl = snprintf(buf + len,
							sizeof(buf) - len,
							"<?%x?>", cur);
					len += xl;
				}
				break;
			}
		} else if (t == DOBJT_STRING) {
			struct	dobj_string	*sobj;
			sobj = MENV_GET_OBJ(env, DOBJ_PTR(cur));
			xl = write_string(sobj, buf + len, sizeof(buf) - len);
			len += xl;
		} else if (t == DOBJT_SYMBOL) {
			struct	dobj_symbol	*sym;
			sym = MENV_GET_OBJ(env, DOBJ_PTR(cur));
			xl = sizeof(buf) - len;
			if (xl > sym->dobj_sym_length) {
				xl = sym->dobj_sym_length;
			}
			memcpy(buf + len, sym->dobj_sym_data, xl);
			len += xl;
		} else if (t == DOBJT_INT) {
			intptr_t	*d = MENV_GET_OBJ(env, DOBJ_PTR(cur));
			xl = snprintf(buf + len, sizeof(buf) - len, "%ld", *d);
			len += xl;
		} else if (t == DOBJT_DOUBLE) {
			double	*d = MENV_GET_OBJ(env, DOBJ_PTR(cur));
			xl = snprintf(buf + len, sizeof(buf) - len, "%f", *d);
			len += xl;
		} else {
			xl = snprintf(buf + len,
					sizeof(buf) - len, "<?t%d?>", t);
			len += xl;
		}
next:
		ptr = vector_append_obj(&fmtvect, len);
		if (ptr == NULL) {
			goto	fini;
		}
		memcpy(ptr, buf, len);
	}
	*res = alloc_string_dobj(env,
			(char*)fmtvect.vect_base, fmtvect.vect_cur);
	err = 0;

fini:
	FINI_VECTOR(&dfvect);
	FINI_VECTOR(&fmtvect);

	return	(err);
}

